package event;

import java.util.Stack;

/**
 * This keeps track of events that can be undone and redone.
 *
 * @author Neil Dickson
 */
public class UndoStack {
	private	Stack<Undoable>		undos;
	private	Stack<Undoable>		redos;

	/**
	 * This constructs an UndoStack.
	 */
	public UndoStack() {
		undos = new Stack<Undoable>();
		redos = new Stack<Undoable>();
	}

	/**
	 * This undoes the most-recent undoable event, if any.
	 */
	public void undo() {
		if (canUndo()) {
			Undoable inverse = undos.pop().undo();
			redos.push(inverse);
		}
	}

	/**
	 * This redoes the most-recently undone event if any.
	 */
	public void redo() {
		if (canRedo()) {
			Undoable inverse = redos.pop().undo();
			undos.push(inverse);
		}
	}

	/**
	 * This adds an event that can be undone to the stack of undos and clears the stack of redos
	 * @param event	the event that was done and can be undone
	 */
	public void add(Undoable event) {
		undos.push(event);
		redos.clear();
	}

	/**
	 * @return the number of events that can be undone
	 */
	public int getUndoSize() {
		return undos.size();
	}

	/**
	 * @return the number of events that can be redone
	 */
	public int getRedoSize() {
		return redos.size();
	}

	/**
	 * @return true iff there is at least one event that can be undone
	 */
	public boolean canUndo() {
		return !undos.isEmpty();
	}

	/**
	 * @return true iff there is at least one event that can be redone
	 */
	public boolean canRedo() {
		return !redos.isEmpty();
	}

	/**
	 * @return an array of Strings describing all of the undos
	 */
	public String[] getUndoDescriptions() {
		String[] descriptions = new String[undos.size()];
		for (int i=0;i<undos.size();++i) {
			descriptions[i] = undos.get(i).getDescription();
		}
		return descriptions;
	}

	/**
	 * @return an array of Strings describing all of the redos
	 */
	public String[] getRedoDescriptions() {
		String[] descriptions = new String[redos.size()];
		for (int i=0;i<redos.size();++i) {
			descriptions[i] = redos.get(i).getDescription();
		}
		return descriptions;
	}
	/**
	 * @return a String describing the most-recent undoable event, or null if none
	 */
	public String getUndoDescription() {
		if (canUndo()) {
			return undos.peek().getDescription();
		}
		return null;
	}

	/**
	 * @return a String describing the most-recently undone event, or null if none
	 */
	public String getRedoDescription() {
		if (canRedo()) {
			return redos.peek().getDescription();
		}
		return null;
	}

	/**
	 * @return the {@link Undoable} at the top of the {@link Stack} of undos
	 */
	public Undoable getUndoTop() {
		if (canUndo()) {
			return undos.peek();
		}
		else {
			return null;
		}
	}

	/**
	 * Merges the specified event with the one at the top of the {@link Stack} of undos, using an {@link UndoSet}
	 * @param event the event to merge with the most-recent undoable event
	 */
	public void mergeWithUndoTop(Undoable event) {
		Undoable previous = undos.peek();
		if (previous instanceof UndoSet) {
			((UndoSet)previous).add(event);
		}
		else {
			undos.pop();
			undos.push(new UndoSet(new Undoable[]{previous,event},event.getDescription()));
		}
		redos.clear();
	}

	/**
	 * Clears all undos and redos.
	 */
	public void clear() {
		undos.clear();
		redos.clear();
	}
}
